<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/value/histogram_set.html">

<script>
'use strict';
tr.exportTo('tr.v', function() {
  /*
   * See also HistogramSet.groupHistogramsRecursively().
   * See also tr.v.ui.HistogramSetTableRow.
   */
  class HistogramSetHierarchy {
    /**
     * @param {string} name
     */
    constructor(name) {
      this.name = name;
      this.description = '';
      this.depth = 0;
      this.subRows = [];
      this.columns = new Map();
    }

    * walk() {
      yield this;
      for (const row of this.subRows) yield* row.walk();
    }

    static* walkAll(rootRows) {
      for (const rootRow of rootRows) yield* rootRow.walk();
    }

    /**
     * Build table rows recursively from grouped Histograms.
     *
     * @param {!(HistogramArray|HistogramArrayMap)}
     * @returns {!Array.<!HistogramSetHierarchy>}
     */
    static build(histogramArrayMap) {
      const rootRows = [];
      HistogramSetHierarchy.buildInternal_(histogramArrayMap, [], rootRows);

      const histograms = new tr.v.HistogramSet();

      for (const row of HistogramSetHierarchy.walkAll(rootRows)) {
        for (const hist of row.columns.values()) {
          if (!(hist instanceof tr.v.Histogram)) continue;
          histograms.addHistogram(hist);
        }
      }

      histograms.deduplicateDiagnostics();

      for (const row of HistogramSetHierarchy.walkAll(rootRows)) {
        row.maybeRebin_();
      }

      return rootRows;
    }

    maybeRebin_() {
      // if all of |this| row's columns are single-bin, then re-bin all of them.
      const dataRange = new tr.b.math.Range();
      for (const hist of this.columns.values()) {
        if (!(hist instanceof tr.v.Histogram)) continue;
        if (hist.allBins.length > 1) return;  // don't re-bin
        if (hist.numValues === 0) continue;  // ignore hist
        dataRange.addValue(hist.min);
        dataRange.addValue(hist.max);
      }

      dataRange.addValue(tr.b.math.lesserWholeNumber(dataRange.min));
      dataRange.addValue(tr.b.math.greaterWholeNumber(dataRange.max));

      if (dataRange.min === dataRange.max) return;  // don't rebin

      const boundaries = tr.v.HistogramBinBoundaries.createLinear(
          dataRange.min, dataRange.max, tr.v.DEFAULT_REBINNED_COUNT);

      for (const [name, hist] of this.columns) {
        if (!(hist instanceof tr.v.Histogram)) continue;
        this.columns.set(name, hist.rebin(boundaries));
      }
    }

    static mergeHistogramDownHierarchy_(histogram, hierarchy, columnName) {
      // Track the path down the grouping tree to each Histogram,
      // but only start tracking the path at the grouping level that
      // corresponds to the Histogram NAME Grouping.
      for (const row of hierarchy) {
        if (!row.description) {
          row.description = histogram.description;
        }

        const existing = row.columns.get(columnName);

        if (existing === undefined) {
          row.columns.set(columnName, histogram.clone());
          continue;
        }

        if (existing instanceof tr.v.HistogramSet) {
          // There have already been unmergeable histograms.
          existing.addHistogram(histogram);
          continue;
        }

        if (!existing.canAddHistogram(histogram)) {
          // TODO(benjhayden) Remove?
          const unmergeableHistograms = new tr.v.HistogramSet([histogram]);
          row.columns.set(columnName, unmergeableHistograms);
          continue;
        }

        existing.addHistogram(histogram);
      }
    }

    static buildInternal_(
        histogramArrayMap, hierarchy, rootRows) {
      for (const [name, histograms] of histogramArrayMap) {
        if (histograms instanceof Array) {
          // This recursion base case corresponds to the recursion base case of
          // groupHistogramsRecursively(). The last groupingCallback is always
          // getDisplayLabel, which defines the columns of the table and is
          // unskippable.
          for (const histogram of histograms) {
            HistogramSetHierarchy.mergeHistogramDownHierarchy_(
                histogram, hierarchy, name);
          }
        } else if (histograms instanceof Map) {
          // |histograms| is actually a nested histogramArrayMap.
          const row = new HistogramSetHierarchy(name);
          row.depth = hierarchy.length;
          hierarchy.push(row);
          HistogramSetHierarchy.buildInternal_(histograms, hierarchy, rootRows);
          hierarchy.pop();

          if (hierarchy.length === 0) {
            rootRows.push(row);
          } else {
            const parentRow = hierarchy[hierarchy.length - 1];
            parentRow.subRows.push(row);
          }
        }
      }
    }
  }

  return {HistogramSetHierarchy};
});
</script>
